#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
价格策略选择器
基于大模型交互的智能价格策略选择系统
"""

import pandas as pd
import numpy as np
from typing import Dict, List, Optional, Tuple, Any
from dataclasses import dataclass
import json
import logging
from datetime import datetime

from ..models.chatgpt_client import ChatGPTClient
from ..models.qwen_client import QwenClient

logger = logging.getLogger(__name__)

@dataclass
class ProductMetrics:
    """产品指标数据"""
    product_name: str
    partition_date: str
    stock_nums: int
    avg_sales_cnt_3days: float
    expected_inventory_turnover: float
    gross_margin_3_day: float
    net_profit_margin_3_day: float
    chaseup_cost_rate_3_day: float

@dataclass
class PriceStrategy:
    """价格策略"""
    target: float  # 目标竞争力 (0-1)
    bi_ratio: float  # BI价距 (0-1)
    inquiry_compete_rate: float  # 竞争力 (0-1)
    cost_rate_estimated: float  # 费用率 (0-1)
    efficiency: float  # 费用效率 = 竞争力/费用率

@dataclass
class StrategyRecommendation:
    """策略推荐结果"""
    product_name: str
    selected_strategy: PriceStrategy
    reason: str
    alternative_strategies: List[PriceStrategy]
    constraints_applied: Dict[str, Any]
    efficiency_score: float

class PriceStrategySelector:
    """价格策略选择器"""
    
    def __init__(self, llm_provider: str = "qwen"):
        """
        初始化策略选择器
        
        Args:
            llm_provider: 大模型提供商 ("qwen" 或 "chatgpt")
        """
        self.llm_provider = llm_provider
        if llm_provider == "qwen":
            self.llm_client = QwenClient()
        else:
            self.llm_client = ChatGPTClient()
        
        # BI价距限制规则
        self.bi_ratio_limits = {
            "12": 0.95,  # 12-13系列
            "13": 0.95,
            "14": 0.97,  # 14-15系列
            "15": 0.97,
            "16": 0.98   # 16系列
        }
        
        # 库存和流速区间定义
        self.stock_ranges = {
            "low": (0, 2000),
            "medium": (2000, 3000),
            "high": (3000, 5000),
            "very_high": (5000, float('inf'))
        }
        
        self.velocity_ranges = {
            "low": (0, 100),
            "medium": (100, 200),
            "high": (200, 500),
            "very_high": (500, float('inf'))
        }
    
    def get_bi_ratio_limit(self, product_name: str) -> float:
        """根据产品名称获取BI价距限制"""
        for series, limit in self.bi_ratio_limits.items():
            if series in product_name:
                return limit
        return 0.95  # 默认限制
    
    def categorize_stock_level(self, stock_nums: int) -> str:
        """分类库存水平"""
        for level, (min_val, max_val) in self.stock_ranges.items():
            if min_val <= stock_nums < max_val:
                return level
        return "very_high"
    
    def categorize_velocity_level(self, avg_sales_cnt: float) -> str:
        """分类流速水平"""
        for level, (min_val, max_val) in self.velocity_ranges.items():
            if min_val <= avg_sales_cnt < max_val:
                return level
        return "very_high"
    
    def filter_strategies_by_constraints(
        self,
        strategies: List[PriceStrategy],
        metrics: ProductMetrics
    ) -> List[PriceStrategy]:
        """根据规则筛选策略，若无结果则逐步放宽（BI价距→竞争力→费用率），直至至少返回一个。"""
        # 基础阈值
        base_cost_limit = metrics.net_profit_margin_3_day / 100.0
        base_comp_limit = 0.4 if metrics.expected_inventory_turnover > 20 else 1.0
        base_bi_limit = self.get_bi_ratio_limit(metrics.product_name)

        def apply_once(bi_limit: float, comp_limit: float, extra_cost_margin: float) -> List[PriceStrategy]:
            picked: List[PriceStrategy] = []
            eff_cost_limit = base_cost_limit + extra_cost_margin
            for s in strategies:
                if (
                    s.cost_rate_estimated <= eff_cost_limit and
                    s.inquiry_compete_rate <= comp_limit and
                    s.bi_ratio <= bi_limit
                ):
                    picked.append(s)
            return picked

        def prefer_high_comp(strategies_list: List[PriceStrategy]) -> List[PriceStrategy]:
            """在周转<20天时，优先返回竞争力>40%的子集（若存在）。"""
            if metrics.expected_inventory_turnover < 20:
                preferred = [s for s in strategies_list if s.inquiry_compete_rate > 0.40]
                if preferred:
                    return preferred
            return strategies_list

        # 1) 严格条件
        result = apply_once(base_bi_limit, base_comp_limit, 0.0)
        if result:
            result = prefer_high_comp(result)
            return result

        # 2) 放宽 BI（每次+0.01，最高到0.995）
        logger.warning(f"产品 {metrics.product_name} 在严格约束下无可用策略，开始放宽限制")
        bi_limit = base_bi_limit
        while bi_limit < 0.995 and not result:
            bi_limit = min(bi_limit + 0.01, 0.995)
            result = apply_once(bi_limit, base_comp_limit, 0.0)
        if result:
            result = prefer_high_comp(result)
            return result

        # 3) 放宽竞争力上限（每次+0.05，直到1.0）
        comp_limit = base_comp_limit
        while comp_limit < 1.0 and not result:
            comp_limit = min(comp_limit + 0.05, 1.0)
            result = apply_once(bi_limit, comp_limit, 0.0)
        if result:
            result = prefer_high_comp(result)
            return result

        # 4) 放宽费用率阈值（每次+0.01，最多+0.05），优先代价最小的放宽
        extra_cost = 0.0
        while extra_cost < 0.051 and not result:
            extra_cost = round(extra_cost + 0.01, 3)
            result = apply_once(bi_limit, comp_limit, extra_cost)
        if result:
            result = prefer_high_comp(result)
            return result

        # 5) 最后兜底：仅满足最低成本率（无放宽），选择“高竞争力优先，其次效率”的单一项
        fallback = [s for s in strategies if s.cost_rate_estimated <= base_cost_limit]
        if fallback:
            fallback = prefer_high_comp(fallback)
            fallback.sort(key=lambda s: (s.inquiry_compete_rate, s.efficiency), reverse=True)
            return [fallback[0]]
        # 如果连成本率都无法满足，返回全体中“竞争力优先，其次效率”的一个
        any_pick = sorted(strategies, key=lambda s: (s.inquiry_compete_rate, s.efficiency), reverse=True)
        return any_pick[:1]
    
    def calculate_strategy_efficiency(self, strategy: PriceStrategy) -> float:
        """计算策略效率分数"""
        if strategy.cost_rate_estimated == 0:
            return float('inf')
        return strategy.inquiry_compete_rate / strategy.cost_rate_estimated
    
    def build_llm_prompt(
        self, 
        metrics: ProductMetrics, 
        available_strategies: List[PriceStrategy],
        stock_level: str,
        velocity_level: str
    ) -> str:
        """构建大模型交互的prompt"""
        
        # 格式化策略数据
        strategies_text = ""
        for i, strategy in enumerate(available_strategies, 1):
            strategies_text += f"""
策略{i}:
- 目标竞争力: {strategy.target:.1%}
- BI价距: {strategy.bi_ratio:.1%}
- 竞争力: {strategy.inquiry_compete_rate:.1%}
- 费用率: {strategy.cost_rate_estimated:.1%}
- 费用效率: {strategy.efficiency:.2f}
"""
        
        prompt = f"""
你是一个专业的手机价格策略分析师。请根据以下产品指标和可用策略，为每个型号选择最优的价格策略。

产品信息:
- 型号: {metrics.product_name}
- 日期: {metrics.partition_date}
- 库存量: {metrics.stock_nums:,} 台
- 近3天日均出库量: {metrics.avg_sales_cnt_3days:.1f} 台
- 预计周转天数: {metrics.expected_inventory_turnover:.1f} 天
- 近3天毛利率: {metrics.gross_margin_3_day:.1f}%
- 近3天净利率: {metrics.net_profit_margin_3_day:.1f}%
- 近3天追价费用率: {metrics.chaseup_cost_rate_3_day:.1f}%

库存水平: {stock_level} (低/中/高/超高)
流速水平: {velocity_level} (低/中/高/超高)

可用价格策略:
{strategies_text}

选择原则:
1. 费用率必须小于近3天净利率 ({metrics.net_profit_margin_3_day:.1f}%)
2. 预计周转天数>20天时，竞争力不能超过40%
3. BI价距限制: 12-13系列≤95%, 14-15系列≤97%, 16系列≤98%
4. 库存量越大，竞争力要求越低
5. 近3天日均出库量越大，可以适当提高竞争力
6. 优先选择费用效率(竞争力/费用率)最高的策略

请返回JSON格式的结果:
{{
    "selected_strategy_index": 策略编号,
    "reason": "选择原因(详细说明)",
    "alternative_strategies": [备选策略编号列表],
    "constraints_applied": {{
        "cost_rate_limit": {metrics.net_profit_margin_3_day/100:.3f},
        "competitiveness_limit": {"0.4" if metrics.expected_inventory_turnover > 20 else "1.0"},
        "bi_ratio_limit": "{self.get_bi_ratio_limit(metrics.product_name):.2f}",
        "stock_level": "{stock_level}",
        "velocity_level": "{velocity_level}"
    }},
    "efficiency_score": 费用效率分数
}}
"""
        return prompt
    
    def parse_llm_response(self, response: str) -> Dict[str, Any]:
        """解析大模型返回的JSON结果"""
        try:
            # 尝试直接解析JSON
            result = json.loads(response)
            return result
        except json.JSONDecodeError:
            # 如果直接解析失败，尝试提取JSON部分
            import re
            json_match = re.search(r'\{.*\}', response, re.DOTALL)
            if json_match:
                try:
                    result = json.loads(json_match.group())
                    return result
                except json.JSONDecodeError:
                    logger.error(f"无法解析大模型返回的JSON: {response}")
                    return None
            else:
                logger.error(f"大模型返回结果中未找到JSON格式: {response}")
                return None
    
    def select_strategy_for_product(
        self, 
        metrics: ProductMetrics, 
        available_strategies: List[PriceStrategy]
    ) -> Optional[StrategyRecommendation]:
        """为单个产品选择价格策略"""
        try:
            # 计算策略效率
            for strategy in available_strategies:
                strategy.efficiency = self.calculate_strategy_efficiency(strategy)
            
            # 根据约束条件筛选策略
            filtered_strategies = self.filter_strategies_by_constraints(available_strategies, metrics)
            
            if not filtered_strategies:
                logger.warning(f"产品 {metrics.product_name} 没有满足约束条件的策略")
                return None
            
            # 分类库存和流速水平
            stock_level = self.categorize_stock_level(metrics.stock_nums)
            velocity_level = self.categorize_velocity_level(metrics.avg_sales_cnt_3days)
            
            # 构建prompt并调用大模型
            prompt = self.build_llm_prompt(metrics, filtered_strategies, stock_level, velocity_level)
            
            try:
                logger.info(f"正在为产品 {metrics.product_name} 调用大模型选择策略...")
                response = self.llm_client.ask(prompt, scene="factual", temperature=0.3)
                
                # 解析大模型返回结果
                llm_result = self.parse_llm_response(response)
                
                if not llm_result:
                    # 如果大模型解析失败，使用规则选择
                    logger.warning(f"大模型解析失败，使用规则选择策略")
                    return self._rule_based_selection(metrics, filtered_strategies)
                
                # 根据大模型结果选择策略
                selected_index = llm_result.get("selected_strategy_index", 1) - 1  # 转换为0基索引
                if 0 <= selected_index < len(filtered_strategies):
                    selected_strategy = filtered_strategies[selected_index]
                else:
                    # 如果索引无效，选择效率最高的策略
                    selected_strategy = max(filtered_strategies, key=lambda s: s.efficiency)
                
                # 构建推荐结果
                recommendation = StrategyRecommendation(
                    product_name=metrics.product_name,
                    selected_strategy=selected_strategy,
                    reason=llm_result.get("reason", "基于规则选择"),
                    alternative_strategies=filtered_strategies[:3],  # 前3个备选
                    constraints_applied=llm_result.get("constraints_applied", {}),
                    efficiency_score=llm_result.get("efficiency_score", selected_strategy.efficiency)
                )
                return recommendation
                    
            except Exception as e:
                # 不再隐藏错误，直接抛出给上层，由UI直接展示真实错误
                logger.error(f"大模型调用失败: {str(e)}")
                raise
            
        except Exception as e:
            logger.error(f"为产品 {metrics.product_name} 选择策略时出错: {str(e)}")
            return None
    
    def _rule_based_selection(
        self, 
        metrics: ProductMetrics, 
        strategies: List[PriceStrategy]
    ) -> StrategyRecommendation:
        """基于规则的策略选择（备用方案）"""
        # 按效率排序
        strategies.sort(key=lambda s: s.efficiency, reverse=True)
        
        selected_strategy = strategies[0]
        
        # 生成选择原因
        reason_parts = []
        if metrics.expected_inventory_turnover > 20:
            reason_parts.append(f"预计周转天数{metrics.expected_inventory_turnover:.1f}天>20天，选择竞争力≤40%的策略")
        
        stock_level = self.categorize_stock_level(metrics.stock_nums)
        if stock_level in ["high", "very_high"]:
            reason_parts.append(f"库存{metrics.stock_nums}台较高，选择竞争力相对较低的策略")
        
        velocity_level = self.categorize_velocity_level(metrics.avg_sales_cnt_3days)
        if velocity_level in ["high", "very_high"]:
            reason_parts.append(f"近3天日均出库{metrics.avg_sales_cnt_3days:.1f}台较高，可适当提高竞争力")
        
        reason_parts.append(f"选择费用效率最高的策略({selected_strategy.efficiency:.2f})")
        
        return StrategyRecommendation(
            product_name=metrics.product_name,
            selected_strategy=selected_strategy,
            reason="; ".join(reason_parts),
            alternative_strategies=strategies[1:4],
            constraints_applied={
                "cost_rate_limit": metrics.net_profit_margin_3_day / 100,
                "competitiveness_limit": 0.4 if metrics.expected_inventory_turnover > 20 else 1.0,
                "bi_ratio_limit": self.get_bi_ratio_limit(metrics.product_name),
                "stock_level": stock_level,
                "velocity_level": velocity_level
            },
            efficiency_score=selected_strategy.efficiency
        )
    
    def select_strategies_for_products(
        self, 
        products_metrics: List[ProductMetrics],
        products_strategies: Dict[str, List[PriceStrategy]]
    ) -> List[StrategyRecommendation]:
        """为多个产品选择价格策略"""
        recommendations = []
        
        for metrics in products_metrics:
            if metrics.product_name in products_strategies:
                recommendation = self.select_strategy_for_product(
                    metrics, 
                    products_strategies[metrics.product_name]
                )
                if recommendation:
                    recommendations.append(recommendation)
        
        return recommendations
